package com.kmxd.ams.infra.seal.utils;

import cn.hutool.core.util.ObjectUtil;
import com.kmxd.ams.infra.seal.setting.SealSetting;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.Pair;

/**
 * 档号章工具类
 *
 * @author zuolg
 */
@Data
@Accessors(chain = true)
public class ArchPdfSeal {
  /** 行 */
  private int row;

  /** 列 */
  private int column;

  /** x 坐标 */
  private int x;

  /** y 坐标 */
  private int y;

  /** 当前单元格的父级单元格所在的列 */
  private int belongColumn = 1;

  /** 单元格宽度,对于有从属单元格的，其宽度由从属单元格决定 */
  private int width;
  /** 、 合并单元格的情况会用到，默认为行高 */
  private int height;
  /** 单元格内容 */
  private String content;
  /** 是否水平居中 */
  private boolean textAlign;

  public ArchPdfSeal(int row, int column, int width, int belongColumn) {
    this.row = row;
    this.column = column;
    this.width = width;
    this.belongColumn = belongColumn;
  }

  public ArchPdfSeal() {}

  /**
   * 根据配置信息获取档号章
   *
   * @param table 表格属性
   * @param tableRows 表格内容
   */
  @SneakyThrows
  public static BufferedImage getArchSeal(SealSetting table, List<List<String>> tableRows) {
    checkHead(table);
    List<ArchPdfSeal> headCells = table.getHeadCells();
    int[] mergeColumns = table.getMergeColumns();
    Map<Integer, List<ArchPdfSeal>> rows =
        headCells.stream().collect(Collectors.groupingBy(ArchPdfSeal::getRow));
    // 表头的最后一行实际有多少个单元格，有合并单元格的情况下按垂直投影的方式获取列；
    List<ArchPdfSeal> actualLastHeadRowColumnCell = findHeadColumns(headCells);
    HashMap<Integer, Integer> headRealRowHead = getHeadRowHeight(rows, table);
    HashMap<Integer, Integer> contentRowHeight = getRowHeight(tableRows, table);
    // 处理表头
    dealTableHead(rows, table, actualLastHeadRowColumnCell, headCells, headRealRowHead);
    // 生成表格各单元格内容对象
    List<ArchPdfSeal> contents =
        getTableContent(tableRows, actualLastHeadRowColumnCell, rows.size(), contentRowHeight);
    // 合并单元格
    List<ArchPdfSeal> cells = mergeCells(contents, mergeColumns, headCells);
    // 计算表格高度
    Integer tableHeight =
        Stream.of(headRealRowHead.values(), contentRowHeight.values())
            .flatMap(Collection::stream)
            .reduce(Integer::sum)
            .orElseThrow(() -> new RuntimeException("表格高度错误"));
    // 绘制表格
    return creatArchSeal(rows.size(), cells, table, tableHeight);
  }

  /**
   * 表头格式校验
   *
   * @param table 表头参数信息
   */
  private static void checkHead(SealSetting table) {
    if (Objects.isNull(table.getHeaderFont())) {
      table.setHeaderFont(new Font("楷体", Font.BOLD, 15));
    }
    if (Objects.isNull(table.getCellFont())) {
      table.setHeaderFont(new Font("宋体", Font.PLAIN, 12));
    }
    if (table.getRowHeight() <= 10) {
      table.setRowHeight(10);
    }
    if (table.getMarginX() < 0 || table.getMarginY() < 0) {
      throw new RuntimeException("表格边距不可小于0");
    }
    table
        .getHeadCells()
        .forEach(
            head -> {
              if (head.getRow() <= 0) {
                throw new RuntimeException("行不可小于0" + head.getRow() + "," + head.getColumn());
              }
              if (head.getColumn() <= 0) {
                throw new RuntimeException("列不可小于0" + head.getRow() + "," + head.getColumn());
              }
              if (head.getBelongColumn() < 0) {
                throw new RuntimeException("从属单元格列不可小于0" + head.getRow() + "," + head.getColumn());
              }
              if (head.getWidth() < 0) {
                throw new RuntimeException("列宽不可小于0" + head.getRow() + "," + head.getColumn());
              }
            });
    checkBelongs(table.getHeadCells());
    ImmutableTriple<ArrayList<Integer>, ArrayList<Integer>, ArrayList<Integer>> triple =
        table.getHeadCells().stream()
            .reduce(
                ImmutableTriple.of(new ArrayList<>(), new ArrayList<>(), new ArrayList<>()),
                (a, b) -> {
                  a.getLeft().add(b.getRow());
                  a.getMiddle().add(b.getColumn());
                  a.getRight().add(b.getBelongColumn());
                  return a;
                },
                (a, b) -> a);
    checkConsistency(triple.getLeft());
    checkConsistency(triple.getMiddle());
    table.getHeadCells().stream()
        .map(ArchPdfSeal::getBelongColumn)
        .distinct()
        .filter(column -> column > 0)
        .filter(column -> !triple.getMiddle().contains(column))
        .findAny()
        .ifPresent(
            a -> {
              throw new RuntimeException("从属单元格" + a + "列不存在");
            });
  }

  /**
   * 表格连续性校验
   *
   * @param data 行或列序列
   */
  private static void checkConsistency(ArrayList<Integer> data) {
    List<Integer> collect = data.stream().distinct().sorted().collect(Collectors.toList());
    for (int i = 0; i < collect.size() - 1; i++) {
      Integer integer = collect.get(i);
      if (!collect.get(i + 1).equals(integer + 1)) {
        throw new RuntimeException("行或者列不连续");
      }
    }
  }

  /**
   * 从属单元格校验
   *
   * @param headCells 表头
   */
  private static void checkBelongs(List<ArchPdfSeal> headCells) {
    Map<Integer, List<ArchPdfSeal>> collect =
        headCells.stream().collect(Collectors.groupingBy(ArchPdfSeal::getRow));
    collect.forEach(
        (key, value) -> {
          List<ArchPdfSeal> rowCells =
              value.stream()
                  .sorted(Comparator.comparing(ArchPdfSeal::getColumn))
                  .collect(Collectors.toList());
          int belongColumn = 0;
          for (ArchPdfSeal cell : rowCells) {
            if (cell.getBelongColumn() >= belongColumn) {
              belongColumn = cell.getBelongColumn();
              continue;
            }
            throw new RuntimeException("从属单元格配置错误" + cell.getRow() + "," + cell.getColumn());
          }
        });
  }

  /**
   * 处理表头
   *
   * @param rows 表头行内容
   * @param table 行高
   * @param headRowColumnCell 表头单元格
   * @param headCells 表头单元格
   */
  private static void dealTableHead(
      Map<Integer, List<ArchPdfSeal>> rows,
      SealSetting table,
      List<ArchPdfSeal> headRowColumnCell,
      List<ArchPdfSeal> headCells,
      HashMap<Integer, Integer> headRealRowHead) {
    int marginX = table.getMarginX();
    int marginY = table.getMarginY();
    rows.keySet().stream()
        .sorted()
        .forEach(
            row -> {
              List<ArchPdfSeal> perRowCells =
                  rows.get(row).stream()
                      .sorted(Comparator.comparing(ArchPdfSeal::getColumn))
                      .collect(Collectors.toList());
              int startFrom = marginX;
              // 非第一行的情况，有可能起始列不是第一列，这里需要取前几列的宽度作为起始宽度
              if (row > 1 && perRowCells.get(0).getColumn() > 1) {
                int column = perRowCells.get(0).getColumn();
                int offset =
                    IntStream.range(1, column)
                        .map(index -> headRowColumnCell.get(index - 1).getWidth())
                        .sum();
                startFrom = marginX + offset;
              }
              for (ArchPdfSeal cell : perRowCells) {
                // 获取从属单元格,单元格的宽度由从属单元格确定
                List<ArchPdfSeal> attached = getCellSonCells(headCells, cell);
                int cellWidth = attached.stream().mapToInt(ArchPdfSeal::getWidth).sum();
                cell.setHeight(headRealRowHead.get(row));
                // 如果该单元格有从属，则高度为行高，反之为单元格所跨行数*行高
                // attached
                //  长度为1：当不是自己时才说明该单元格是他的从属
                //  长度大于1：有从属
                if (attached.size() == 1 && attached.get(0).equals(cell)) {
                  int sum =
                      IntStream.rangeClosed(cell.getRow(), rows.size())
                          .map(headRealRowHead::get)
                          .sum();
                  cell.setHeight(sum);
                }
                if (cellWidth <= 0) {
                  throw new RuntimeException(
                      "行:" + cell.getRow() + " 列:" + cell.getColumn() + "宽度必须大于0");
                }
                cell.setWidth(cellWidth);
                cell.setX(startFrom);
                startFrom += cellWidth;
                int sum = IntStream.rangeClosed(1, row - 1).map(headRealRowHead::get).sum();
                cell.setY(sum + marginY);
              }
            });
  }

  /**
   * 计算表头行高
   *
   * @param rows 表头行
   * @param table 表属性
   * @return 表头行与行高的对应 key:行 value:行高
   */
  private static HashMap<Integer, Integer> getHeadRowHeight(
      Map<Integer, List<ArchPdfSeal>> rows, SealSetting table) {
    return rows.keySet().stream()
        .map(
            row1 -> {
              Integer cellRow =
                  rows.get(row1).stream()
                      .map(cell -> StringUtils.countMatches(cell.getContent(), "\n"))
                      .max(Integer::compare)
                      .orElse(1);
              int appendHeight = cellRow * table.getHeaderFont().getSize();
              return Pair.of(row1, table.getRowHeight() + appendHeight);
            })
        .reduce(
            new HashMap<>(16),
            (a, b) -> {
              a.put(b.getLeft(), b.getRight());
              return a;
            },
            (c, d) -> c);
  }

  /**
   * 生成表格各单元格内容对象
   *
   * @param rowContents 表格内容
   * @param headRowColumnCell 表头单元格
   * @param headRowSize 表头行数
   * @param contentRowHeight 各行的行高关系
   * @return 表格各单元格内容对象
   */
  private static List<ArchPdfSeal> getTableContent(
      List<List<String>> rowContents,
      List<ArchPdfSeal> headRowColumnCell,
      int headRowSize,
      HashMap<Integer, Integer> contentRowHeight) {
    List<ArchPdfSeal> contents = new ArrayList<>();
    for (int i = 0; i < rowContents.size(); i++) {
      List<String> rowContent = rowContents.get(i);
      for (int j = 0; j < rowContent.size(); j++) {
        String cellContent = rowContent.get(j);
        ArchPdfSeal cell = new ArchPdfSeal();
        ArchPdfSeal lastHeadColumnCell = headRowColumnCell.get(j);
        cell.setRow(i + 1 + headRowSize);
        cell.setColumn(lastHeadColumnCell.getColumn());
        cell.setX(lastHeadColumnCell.getX());
        // 单元格纵坐标
        int sum = IntStream.rangeClosed(1, i).map(contentRowHeight::get).sum();
        int y = (lastHeadColumnCell.getY() + lastHeadColumnCell.getHeight()) + sum;
        cell.setY(y);
        cell.setWidth(lastHeadColumnCell.getWidth());
        cell.setTextAlign(true);
        cell.setHeight(contentRowHeight.get(i + 1));
        cell.setContent(cellContent);
        contents.add(cell);
      }
    }
    return contents;
  }

  /**
   * 计算单元格高度
   *
   * @param rowContents 表格内容
   * @param table 表格属性
   * @return 行与行高对应关系 key:行 value:行高
   */
  private static HashMap<Integer, Integer> getRowHeight(
      List<List<String>> rowContents, SealSetting table) {
    return IntStream.rangeClosed(1, rowContents.size())
        .mapToObj(row -> dealRowHeight(rowContents, table, row))
        .reduce(
            new HashMap<>(16),
            (a, b) -> {
              a.put(b.getLeft(), b.getRight());
              return a;
            },
            (c, d) -> c);
  }

  /**
   * 计算单元格高度主方法
   *
   * @param rowContents 表格内容
   * @param table 表格属性
   * @param row 行内容
   * @return 行与行高的对应关系 key:行 value:行高
   */
  private static Pair<Integer, Integer> dealRowHeight(
      List<List<String>> rowContents, SealSetting table, int row) {
    Integer cellRows =
        rowContents.get(row - 1).stream()
            .map(content -> StringUtils.countMatches(content, "\n"))
            .max(Integer::compare)
            .orElse(1);
    int appendHeight = table.getCellFont().getSize() * cellRows;
    return Pair.of(row, table.getRowHeight() + appendHeight);
  }

  /**
   * 生成档号章
   *
   * @param tableCells 表格所有单元格
   * @param table 表格属性
   */
  public static BufferedImage creatArchSeal(
      int headRow, List<ArchPdfSeal> tableCells, SealSetting table, int tabHeight) {
    int marginY = table.getMarginY();
    int marginX = table.getMarginX();
    Map<Integer, List<ArchPdfSeal>> allTableRows =
        tableCells.stream().collect(Collectors.groupingBy(ArchPdfSeal::getRow));
    int allRows = allTableRows.size();
    // 画布高度
    int imageHeight = table.getHeight();
    // 画布宽度
    int tableWidth =
        tableCells.stream()
            .filter(cell -> cell.getRow() == 1)
            .mapToInt(ArchPdfSeal::getWidth)
            .sum();
    int imageWidth = table.getWidth();

    BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
    Graphics2D graphics = image.createGraphics();
    // 默认白色背景
    graphics.fillRect(0, 0, imageWidth, imageHeight);
    if (ObjectUtil.isEmpty(table.getBgColor())) {
      // 设置背景透明
      image =
          graphics
              .getDeviceConfiguration()
              .createCompatibleImage(imageWidth, imageHeight, Transparency.TRANSLUCENT);
      graphics = image.createGraphics();
    } else {
      graphics.setBackground(table.getBgColor());
    }
    // 设置线宽
    graphics.setStroke(new BasicStroke(table.getLineWidth()));

    final Graphics2D g2d = graphics;
    g2d.setColor(table.getLineColor());

    // 抗锯齿（抗锯齿总开关）
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    // 消除文字锯齿，alpha差值（着色)
    g2d.setRenderingHint(
        RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    // 文字抗锯齿
    g2d.setRenderingHint(
        RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
    // 笔划规范化(画线用)
    g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    // 颜色规范化（渐变用）
    g2d.setRenderingHint(
        RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
    // 字体小数开关
    g2d.setRenderingHint(
        RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    // 插值算法，缩放旋转用
    g2d.setRenderingHint(
        RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    // 呈现质量开关
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    // 抖动形状
    g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);

    // 设置线宽
    g2d.setStroke(new BasicStroke(table.getLineWidth()));
    //    g2d.drawLine(0, 0, imageWidth, 0);
    //    g2d.drawLine(0, 0, 0, imageHeight);
    //    g2d.drawLine(0, imageHeight - 1, imageWidth - 1, imageHeight - 1);
    //    g2d.drawLine(imageWidth - 1, 0, imageWidth - 1, imageHeight - 1);

    IntStream.rangeClosed(1, allRows + 1)
        .forEach(
            i -> {
              // 表格最后一行画横线
              if (i == allRows + 1) {
                // 获取第一行的所有单元格
                List<ArchPdfSeal> rowCells = allTableRows.get(1);
                for (ArchPdfSeal cell : rowCells) {
                  g2d.drawLine(
                      cell.getX() - 1, tabHeight, cell.getX() + cell.getWidth(), tabHeight);
                  g2d.drawLine(
                      cell.getX() - 1, tabHeight, cell.getX() + cell.getWidth(), tabHeight);
                }
                return;
              }
              List<ArchPdfSeal> perRowCells = allTableRows.get(i);
              if (i <= headRow) {
                for (int h = 0; h < perRowCells.size(); h++) {
                  ArchPdfSeal cell = perRowCells.get(h);
                  // 表头单元格填充背景色
                  g2d.setColor(new Color(0, 0, 0, 0f));
                  g2d.fillRect(cell.getX(), cell.getY(), cell.getWidth(), cell.getHeight());
                  // 当前单元格是否为本行最后一个
                  boolean lastCellInRow = h == perRowCells.size() - 1;
                  coreArchSeal(g2d, table, table.getHeaderFont(), lastCellInRow, cell);
                }
                return;
              }
              for (int j = 0; j < perRowCells.size(); j++) {
                ArchPdfSeal cell = perRowCells.get(j);
                // 当前单元格是否为本行最后一个
                boolean lastCellInRow = j == perRowCells.size() - 1;
                coreArchSeal(g2d, table, table.getCellFont(), lastCellInRow, cell);
              }
            });

    // 提高文字本身的质量
    //    g2d.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);

    g2d.drawImage(image.getScaledInstance(imageWidth, imageHeight, Image.SCALE_AREA_AVERAGING), 0, 0, null);
    g2d.dispose();
    return image;
  }

  /**
   * 档号章核心方法
   *
   * @param g2d 绘制类
   * @param font 字体信息
   * @param lastCellRow 是否为行内最后一个单元格
   * @param cell 要绘制的单元格
   */
  private static void coreArchSeal(
      Graphics2D g2d, SealSetting table, Font font, boolean lastCellRow, ArchPdfSeal cell) {
    g2d.setColor(table.getLineColor());
    // 每个单元格左起横线
    g2d.drawLine(cell.getX(), cell.getY(), cell.getX() + cell.getWidth(), cell.getY());
    // 每个单元格左起竖线
    g2d.drawLine(cell.getX(), cell.getY(), cell.getX(), cell.getY() + cell.getHeight());
    //    // 每个单元格左起横线
    g2d.drawLine(cell.getX(), cell.getY(), cell.getX() + cell.getWidth(), cell.getY());
    // 每个单元格左起竖线
    g2d.drawLine(cell.getX(), cell.getY(), cell.getX(), cell.getY() + cell.getHeight());
    // 单元格内容
    // 如果是水平居中,根据“字体”计算出实际起始坐标位置
    g2d.setFont(font);
    String content = StringUtils.defaultIfBlank(cell.getContent(), " ");
    String[] split = StringUtils.split(content, "\n");
    for (int i = 0; i < split.length; i++) {
      // 1.计算单元格内容的横坐标，加1是为了防止文字紧贴在单元格上
      int contentX = cell.getX() + 1;
      String cellRowContent = split[i];
      if (cell.isTextAlign()) {
        // 初始执行比较耗时
        FontMetrics fontMetrics = g2d.getFontMetrics();
        int contentLen = fontMetrics.stringWidth(cellRowContent);
        contentX += (cell.getWidth() - contentLen) / 2;
      }
      // 2. 计算单元格纵坐标,默认居中(!!!注意：内容是从下向上从左向右渲染,所在在单元格的基础上又加了字体font.getSize())
      int startY = cell.getY() + font.getSize();
      // 单元格内第一行文字的纵坐标
      int cellFirstRowPosition = (cell.getHeight() - font.getSize() * (split.length)) / 2;
      // 偏移量（在行一行文字纵坐标的基础上进行累加），加1是为了防止每行文字粘在一起。
      int offset = (font.getSize() + 1) * i;
      int contentY = startY + cellFirstRowPosition + offset;
      // 3. 写入单元格内容
      g2d.setColor(table.getFontColor());
      if (ObjectUtil.isNotEmpty(table.getCellFont())) {
        g2d.setFont(table.getCellFont());
      } else {
        g2d.setFont(new Font("宋体", Font.PLAIN, 12));
      }
      g2d.drawString(cellRowContent, contentX, contentY);
      // +1
      g2d.drawString(cellRowContent, contentX, contentY);
    }
    // 每行最后一个单元格的竖线
    if (lastCellRow) {
      g2d.setColor(table.getLineColor());
      // 单元格最右的横坐标
      int cellRightX = cell.getX() + cell.getWidth();
      g2d.drawLine(cellRightX, cell.getY(), cellRightX, cell.getY() + cell.getHeight());
      // +1
      g2d.drawLine(cellRightX, cell.getY(), cellRightX, cell.getY() + cell.getHeight());
    }
  }

  /**
   * 按列合并单元格
   *
   * @param contents 单元格内容
   * @param mergeColumns 要合并的列
   * @param headCells 表头单元格
   */
  private static List<ArchPdfSeal> mergeCells(
      List<ArchPdfSeal> contents, int[] mergeColumns, List<ArchPdfSeal> headCells) {
    if (ArrayUtils.isEmpty(mergeColumns)) {
      contents.addAll(headCells);
      return contents;
    }
    if (CollectionUtils.isEmpty(contents)) {
      contents.addAll(headCells);
      return contents;
    }
    Map<Integer, List<ArchPdfSeal>> collect =
        contents.stream().collect(Collectors.groupingBy(ArchPdfSeal::getColumn));
    List<ArchPdfSeal> toDel = new ArrayList<>();
    for (int mergeColumn : mergeColumns) {
      List<ArchPdfSeal> cells =
          collect.get(mergeColumn).stream()
              .sorted(Comparator.comparing(ArchPdfSeal::getRow))
              .collect(Collectors.toList());
      if (CollectionUtils.isNotEmpty(cells)) {
        for (int j = 0; j < cells.size() - 1; j++) {
          ArchPdfSeal cell = cells.get(j);
          for (int k = j + 1; k < cells.size(); k++) {
            ArchPdfSeal cellToCompare = cells.get(k);
            if (!StringUtils.equals(cell.getContent(), cellToCompare.getContent())) {
              break;
            }
            cell.setHeight(cell.getHeight() + cellToCompare.getHeight());
            toDel.add(cellToCompare);
          }
        }
      }
    }
    ArrayList<ArchPdfSeal> cells = new ArrayList<>(CollectionUtils.subtract(contents, toDel));
    cells.addAll(headCells);
    return cells;
  }

  /**
   * 获取表头最后一行的所有单元格
   *
   * @param cells 表头所有的单元格
   */
  private static List<ArchPdfSeal> findHeadColumns(List<ArchPdfSeal> cells) {
    Map<Integer, List<ArchPdfSeal>> rows =
        cells.stream().collect(Collectors.groupingBy(ArchPdfSeal::getRow));
    // 取第一行的所有单元格
    List<ArchPdfSeal> firstRow = rows.get(1);
    if (CollectionUtils.isEmpty(firstRow)) {
      throw new RuntimeException("缺少第一行");
    }
    // 按列升序排序
    List<ArchPdfSeal> perRowCells =
        firstRow.stream()
            .sorted(Comparator.comparing(ArchPdfSeal::getColumn))
            .collect(Collectors.toList());
    List<ArchPdfSeal> headColumns = new ArrayList<>();
    for (ArchPdfSeal cell : perRowCells) {
      // 获取每个单元格下的所有从属单元格，如果没有则返回其本身
      headColumns.addAll(getCellSonCells(cells, cell));
    }
    return headColumns;
  }

  /**
   * 获取所有的从属单元格
   *
   * @param cells 所有表头单元格
   * @param cell 要获取从属的单元格
   * @return 该单元格下的从属单元格
   */
  private static List<ArchPdfSeal> getCellSonCells(List<ArchPdfSeal> cells, ArchPdfSeal cell) {
    // 从属单元格：该单元格所在列中的所有单元格
    List<ArchPdfSeal> collect =
        cells.stream()
            .filter(cel -> cel.getRow() == cell.getRow() + 1)
            .filter(cel -> cel.getBelongColumn() == cell.getColumn())
            .collect(Collectors.toList());
    // 没有从属单元格则返回本身
    if (CollectionUtils.isEmpty(collect)) {
      ArrayList<ArchPdfSeal> objects = new ArrayList<>();
      objects.add(cell);
      return objects;
    }
    // 有从属单元格则遍历每一个单元格来获取每个单元格下的从属单元格
    List<List<ArchPdfSeal>> allSonCells =
        collect.stream().map(s -> getCellSonCells(cells, s)).collect(Collectors.toList());
    return allSonCells.stream().flatMap(Collection::stream).collect(Collectors.toList());
  }
}
